/**
 * 通用工具单例
 */
export class Utils {
    /**
     * 获取设备根目录
     */
    getRootUrl() {
        let url = ``
        switch (cc.sys.os) {
            case cc.sys.OS_WINDOWS:
                url = `C:\\\\`

                break
            case cc.sys.OS_ANDROID:
                url = ``

                break
            case cc.sys.OS_IOS:
                url = ``

                break

            default:
                break
        }

        return url
    }

    /**
     * 拷贝对象，只拷贝一层
     * @param obj 待拷贝的对象
     */
    copyObj<T>(obj: T): T {
        // cc.log(`${cc.js.getClassName(this)} copyObj`)

        let newObj = null
        if (obj) {
            if (obj instanceof Array) {
                newObj = [...obj]
            } else if (obj instanceof Object) {
                if (obj.hasOwnProperty) {
                    newObj = {}
                    for (const key in obj) {
                        if (obj.hasOwnProperty(key)) {
                            const element = obj[key]
                            newObj[key] = element
                        }
                    }
                } else {
                    newObj = cc.js.createMap()
                    cc.js.mixin(newObj, obj)
                }
            } else {
                newObj = obj
            }
        } else {
            newObj = obj
        }

        return newObj
    }

    /**
     * 将数值转换成万进制，并舍去小数点，以W结尾
     * @param num 原始值
     */
    convertNumToWan(num: number): string {
        // cc.log(`${cc.js.getClassName(this)} convertNumToWan`)

        let str = num.toString()
        if (num >= 10000) {
            str = `${Math.floor(num * 0.0001)}W`
        }

        return str
    }

    /**
     * 计算年龄
     * @param birthDate 出生日期对象
     */
    analyseAge(birthDate: Date): number {
        // cc.log(`${cc.js.getClassName(this)} analyseAge`)

        let yearBirth = birthDate.getFullYear(),
            monthBirth = birthDate.getMonth() + 1,
            dayBirth = birthDate.getDate(),
            nowDate = new Date(),
            monthNow = nowDate.getMonth() + 1,
            dayNow = nowDate.getDate(),
            age = nowDate.getFullYear() - yearBirth

        if (
            monthNow < monthBirth ||
            (monthNow == monthBirth && dayNow < dayBirth)
        ) {
            age--
        }

        return age
    }

    /**
     * 格式化日期，分隔符为字符串才显示相应的时间
     * @param date 日期对象
     * @param separatorY 年分隔符
     * @param separatorM 月分隔符
     * @param separatorD 日分隔符
     * @param separatorH 时分隔符
     * @param separatorMi 分分隔符
     * @param separatorS 秒分隔符
     * @param separatorMs 秒分隔符
     */
    formatDate(date: Date, separatorY: string = null, separatorM: string = null, separatorD: string = null, separatorH: string = null, separatorMi: string = null, separatorS: string = null, separatorMs: string = null): string {
        // cc.log(`${cc.js.getClassName(this)} formatDate`)

        let dateStr = ``
        if (separatorY + `` === separatorY) {
            dateStr = `${dateStr}${date.getFullYear()}${separatorY}`
        }
        if (separatorM + `` === separatorM) {
            dateStr = `${dateStr}${date.getMonth() + 1}${separatorM}`
        }
        if (separatorD + `` === separatorD) {
            dateStr = `${dateStr}${date.getDate()}${separatorD}`
        }
        if (separatorH + `` === separatorH) {
            dateStr = `${dateStr}${date.getHours()}${separatorH}`
        }
        if (separatorMi + `` === separatorMi) {
            dateStr = `${dateStr}${this.formatInt(`%02d`, date.getMinutes())}${separatorMi}`
        }
        if (separatorS + `` === separatorS) {
            dateStr = `${dateStr}${this.formatInt(`%02d`, date.getSeconds())}${separatorS}`
        }
        if (separatorMs + `` === separatorMs) {
            dateStr = `${dateStr}${this.formatInt(`%02d`, date.getMilliseconds())}${separatorMs}`
        }

        return dateStr
    }

    /**
     * 格式化时间段，分隔符为字符串才显示相应的时间
     * @param time 毫秒数
     * @param separatorY 年分隔符
     * @param separatorM 月分隔符
     * @param separatorD 日分隔符
     * @param separatorH 时分隔符
     * @param separatorMi 分分隔符
     * @param separatorS 秒分隔符
     * @param separatorMs 秒分隔符
     * @param  isOnlyLonggestSeparator 是否只显示最大的时间段
     */
    formatTime(time: number, separatorY: string = null, separatorM: string = null, separatorD: string = null, separatorH: string = null, separatorMi: string = null, separatorS: string = null, separatorMs: string = null, isOnlyLonggestSeparator = false): string {
        // cc.log(`${cc.js.getClassName(this)} formatTime`)

        let timeStr = ``,
            second = Math.floor(time * 0.001),
            min = Math.floor(second / 60),
            hour = Math.floor(min / 60),
            day = Math.floor(hour / 24),
            month = Math.floor(day / 30),
            year = Math.floor(month / 365)
        if (separatorY + `` === separatorY && year) {
            timeStr = `${timeStr}${year}${separatorY}`
            if (isOnlyLonggestSeparator) {
                return timeStr
            }
        }
        if (separatorM + `` === separatorM && month) {
            timeStr = `${timeStr}${month}${separatorM}`
            if (isOnlyLonggestSeparator) {
                return timeStr
            }
        }
        if (separatorD + `` === separatorD && day) {
            timeStr = `${timeStr}${day}${separatorD}`
            if (isOnlyLonggestSeparator) {
                return timeStr
            }
        }
        if (separatorH + `` === separatorH && hour) {
            timeStr = `${timeStr}${hour}${separatorH}`
            if (isOnlyLonggestSeparator) {
                return timeStr
            }
        }
        if (separatorMi + `` === separatorMi && min) {
            timeStr = `${timeStr}${this.formatInt(`%02d`, min)}${separatorMi}`
            if (isOnlyLonggestSeparator) {
                return timeStr
            }
        }
        if (separatorS + `` === separatorS && second) {
            timeStr = `${timeStr}${this.formatInt(`%02d`, second)}${separatorS}`
            if (isOnlyLonggestSeparator) {
                return timeStr
            }
        }
        if (separatorMs + `` === separatorMs && time) {
            timeStr = `${timeStr}${this.formatInt(`%02d`, time)}${separatorMs}`
            if (isOnlyLonggestSeparator) {
                return timeStr
            }
        }

        return timeStr
    }

    /**
     * 根据文本组件，格式化文本长度
     * @param lab 文本组件
     * @param pStr 结尾符
     * @param mLength 字符数，决定了字符串最终长度，一个字符是半个字体大小长度
     * @param fast 是否快速格式化，最终长度不精确
     */
    checkStrLen(lab: cc.Label, pStr: string = `...`, mLength: number = 12, fast: boolean = true): void {
        // Log.d(`${cc.js.getClassName(this)} checkStrLen`)

        if (lab instanceof cc.Label) {
            let fontSize = lab.fontSize
            let mWidth = fontSize * mLength * 0.5
            let oStr = lab.string,
                tarStr = ``,
                tarWidth = 0,
                str = ``,
                curWidth = 0
            for (let i = 0; i < oStr.length; i++) {
                let a = oStr.charAt(i)
                str += a
                if (a.match(/[^\x00-\xff]/ig) != null || /[A-Z]/.test(a)) {
                    curWidth += fontSize
                } else {
                    curWidth += fontSize * 0.5
                }
                if (curWidth >= mWidth && !tarStr) {
                    tarStr = str
                    tarWidth = curWidth
                }
                if (i === oStr.length - 1 && !tarStr) {
                    tarStr = str
                    tarWidth = curWidth
                }
            }
            if (fast) {
                lab.string = curWidth > mWidth ? tarStr + pStr : tarStr
                lab.node.width = tarWidth
            } else {
                lab.string = pStr
                //lab._forceUpdateRenderData()
                let pWidth = lab.node.width
                lab.string = ``
                for (let index = 0; index < oStr.length; index++) {
                    const char = oStr[index];
                    if (char) {
                        let oString = lab.string
                        lab.string += char
                        //lab._forceUpdateRenderData()
                        // nx.logd(lab.string)
                        // nx.logd(lab.node.width)
                        if (lab.node.width >= mWidth - pWidth) {
                            lab.string = oString
                            lab.string += curWidth > mWidth ? pStr : ``
                            break
                        }
                    }
                }
            }
        } else {
        }
    }

    /**
     * 超出文本显示省略号
     * @param date 控制文本长度
     */
    fontNumber(date: string, leng: number = 12): string {
        const length = date.length
        if (length > leng) {
            let str = ``
            str = date.substring(0, leng) + `...`
            return str
        } else {
            return date
        }
    }

    /**
     * 格式化整数 formatInt(`%02d`, time1)
     */
    formatInt(...params: (string | number)[]): string {
        let as = [].slice.call(arguments),
            fmt = as.shift(),
            i = 0
        return fmt.replace(/%(\w)?(\d)?([dfsx])/ig, function (_, a, b, c) {
            let s = b ? new Array(b - 0 + 1).join(a || ``) : ``
            if (c == `d`) s += parseInt(as[i++])
            return b ? s.slice(b * -1) : s
        })
    }

    /**
     * 查找某个数组中与目标数最接近的数
     * @param arr 需要查找的数组
     * @param num 目标数值，查找的是与这个数值最接近的
     */
    findCloseNum(arr: number[], num: number): { index: number; num: number } {
        let index = 0 // 保存最接近数值在数组中的索引
        let d_value = Number.MAX_VALUE // 保存差值绝对值，默认为最大数值
        for (let i = 0; i < arr.length; i++) {
            let new_d_value = Math.abs(arr[i] - num); // 新差值
            if (new_d_value <= d_value) { // 如果新差值绝对值小于等于旧差值绝对值，保存新差值绝对值和索引
                if (new_d_value === d_value && arr[i] < arr[index]) { // 如果数组中两个数值跟目标数值差值一样，取大
                    continue;
                }
                index = i
                d_value = new_d_value
            }
        }
        return { index: index, num: arr[index] } // 返回最接近的数值
    }

    /**
     * 将数组均分，如：[1, 2, 3, 4, 5]均分三份，返回[[1, 2], [3, 4], [5]]
     * @param array 数组
     * @param avgNum 均分份数
     */
    averageArray(array: unknown[], avgNum: number): unknown[] {
        let result = []
        avgNum = Math.ceil(array.length / avgNum)
        for (let i = 0; i < array.length; i += avgNum) {
            let arr = []
            for (let j = 0; j < avgNum; j++) {
                arr.push(array[i + j])
            }
            result.push(arr)
        }

        return result
    }

    /**
     * 将数组交错均分，如：[1, 2, 3, 4, 5]交错均分三份，返回[[1, 4], [2, 5], [3]]
     * @param array 数组
     * @param avgNum 交错均分份数
     */
    interveinAverageArray(array: unknown[], avgNum: number): unknown[] {
        let result = []
        avgNum = Math.ceil(array.length / avgNum)
        for (let index = 0; index < array.length; index += avgNum) {
            let i = index % avgNum
            result[i] = result[i] || []
            result[i].push(array[index])
        }

        return result
    }

    /**
     * 解析json字符串
     * @param json
     */
    parseJSON(json: string): unknown {
        if (typeof json === `string`) {
            try {
                return JSON.parse(json)
            } catch (e) {
                return json
            }
        } else {
            return json
        }
    }

    /**
     * 解析地址后面的参数
     * @param url 地址
     */
    parseUrlParams(url: string): unknown {
        let params = cc.js.createMap()
        if (typeof url === `string`) {
            let indexPos = url.indexOf(`?`) || 0,
                str = url.substring(indexPos + 1),
                strs = str.split(`&`)
            for (let i = 0; i < strs.length; i++) {
                let strValues = strs[i].split(`=`)
                params[strValues[0]] = strValues[1]
            }
        }

        return params
    }

    /**
     * 挂载红点到目标节点
     * @param node 挂载红点的目标节点
     * @param num 显示数值
     */
    setRedPoint(node: cc.Node, num: number): void {
        cc.log(`${cc.js.getClassName(this)} setRedPoint num = ${num}`)

        cc.resources.load(`Prefab/Components/RedPointCpnt`, (err, prefab: cc.Prefab) => {
            if (err) {
                cc.error(err)
            } else {
                let scRpCpnt = (cc.find(`RedPointCpnt`, node) || cc.instantiate(prefab)).getComponent(prefab.data.name)
                !scRpCpnt.node.parent && node.addChild(scRpCpnt.node)
                scRpCpnt.init({
                    num: num,
                })
            }
        })
    }

    /**
     * 抖动节点
     * @param node 节点
     */
    shakeNode(node: cc.Node & { shaking: boolean }): void {
        // cc.log(`${cc.js.getClassName(this)} shakeNode node = `, node)

        if (node.activeInHierarchy && !node.shaking) {
            node.shaking = true
            cc.tween(node)
                .by(0.05, { position: cc.v3(10, 0) })
                .by(0.1, { position: cc.v3(-20, 0) })
                .by(0.05, { position: cc.v3(10, 0) })
                .call(() => {
                    delete node.shaking
                })
                .start()
        }
    }

    /**
     * 使节点具有抽屉运动功能
     * @param node 抽屉节点
     * @param openDrct 打开方向，`"top"down"left"right"`
     * @param openDsts 打开距离，像素
     * @param onCanMoveCall 是否可以移动，点击移动中事件回调，默认为null，如果传入，必须返回bool值，返回true则可以移动，false则不可移动
     * @param onTouchMoveCall 点击移动中事件回调，默认为null
     * @param onTouchUpCall 点击移动结束事件回调，默认为null
     * @param openTime 打开时间，秒，默认0.1
     */
    makeDrawerAct(node: cc.Node & { moving: boolean }, openDrct: string, openDsts: number, onCanMoveCall: () => boolean = null, onTouchMoveCall: () => void = null, onTouchUpCall: () => void = null, openTime: number = 0.1): void {
        // cc.log(`${cc.js.getClassName(this)} makeDrawerAct node = `, node)

        let minTouchMove = 10
        node.on(cc.Node.EventType.TOUCH_MOVE, (event: cc.Touch & cc.Event): void => {
            // cc.log(`${cc.js.getClassName(this)} onTouchMove`)

            let delta = event.getLocation().sub(event.getStartLocation())
            if ((!onCanMoveCall || onCanMoveCall()) && Math.abs(delta.x) >= minTouchMove) {
                node.moving = true

                switch (openDrct) {
                    case `top`:

                        break
                    case `down`:

                        break
                    case `left`:
                        node.x += event.getDelta().x
                        let tarX = node.x
                        tarX = tarX > 0 ? 0 : tarX
                        tarX = tarX < -openDsts ? -openDsts : tarX
                        node.x = tarX

                        break
                    case `right`:

                        break

                    default:
                        break
                }

                onTouchMoveCall && onTouchMoveCall()
            }
        }, this)
        let onTouchUp = (event: cc.Touch & cc.Event): void => {
            if (node.moving) {
                node.moving = false
                event.stopPropagation()

                let delta = event.getLocation().sub(event.getStartLocation())
                switch (openDrct) {
                    case `top`:

                        break
                    case `down`:

                        break
                    case `left`:
                        let tarX
                        if (delta.x > 0) {
                            tarX = 0
                        } else if (delta.x < 0) {
                            tarX = -openDsts
                        }
                        tarX !== undefined && cc.tween(node)
                            .to(openTime, {
                                x: tarX,
                            })
                            .call(() => {
                                node.x = tarX
                            })
                            .start()

                        break
                    case `right`:

                        break

                    default:
                        break
                }

                onTouchUpCall && onTouchUpCall()
            }
        }
        node.on(cc.Node.EventType.TOUCH_END, (event: cc.Touch & cc.Event): void => {
            // cc.log(`${cc.js.getClassName(this)} onTouchEnd`)

            onTouchUp(event)
        }, this)
        node.on(cc.Node.EventType.TOUCH_CANCEL, (event: cc.Touch & cc.Event): void => {
            // cc.log(`${cc.js.getClassName(this)} onTouchCancel`)

            onTouchUp(event)
        }, this)
    }

    /**
     * 从源数据中搜索关键字，返回匹配的源数据
     * @param source 源数据
     * @param keyword 关键字
     */
    searchKeyword(source: string[], keyword: string): string[] {
        // cc.log(`${cc.js.getClassName(this)} searchKeyword`)

        let result = []
        if (source.length) {
            source.forEach(str => {
                if ((new RegExp(keyword)).test(str)) {
                    result.push(str)
                }
            })
        }

        return result
    }

    /**
     * 双指缩放监听（可以用 touchNode.targetOff(touchNode) 来取消）
     * @param touchNode 操作区域
     * @param scaleNode 缩放对象
     * @param call 缩放后回调
     */
    onMultiTouchScaler(touchNode: cc.Node, scaleNode: cc.Node, maxScale: number, call: () => void = null) {
        cc.log(`${cc.js.getClassName(this)} onMultiTouchScaler touchNode = `, touchNode, ` scaleNode = `, scaleNode)
        if(touchNode === scaleNode)
            cc.warn(`${cc.js.getClassName(this)} onMultiTouchScaler touchNode and scaleNode are the same node!`)

        let parent = cc.Canvas.instance.node.parent
        touchNode.on(cc.Node.EventType.TOUCH_MOVE, (event: cc.Event.EventTouch) => {
            let touches = event.getTouches()
            if (touches.length >= 2) {
                let touch1 = touches[0], touch2 = touches[1],
                    delta1 = touch1.getDelta(),
                    delta2 = touch2.getDelta(),
                    touchPoint1 = parent.convertToNodeSpaceAR(touch1.getLocation()),
                    touchPoint2 = parent.convertToNodeSpaceAR(touch2.getLocation()),
                    oScale = scaleNode.scale
                //缩放
                let distance = touchPoint1.sub(touchPoint2)
                let delta = delta1.sub(delta2)
                let scale = oScale
                if (Math.abs(distance.x) > Math.abs(distance.y)) {
                    scale = (distance.x + delta.x * 0.5) / distance.x * scaleNode.scale
                } else {
                    scale = (distance.y + delta.y * 0.5) / distance.y * scaleNode.scale
                }
                scaleNode.scale = cc.misc.clampf(scale, 1, maxScale)
                scaleNode.x -= (scaleNode.scale - oScale) * scaleNode.width * 0.5
                scaleNode.y -= (scaleNode.scale - oScale) * scaleNode.height * 0.5
                cc.log(`${cc.js.getClassName(this)} onMultiTouchScaler scaleNode.scale = ${scaleNode.scale} scaleNode.width = ${scaleNode.width} scaleNode.height = ${scaleNode.height} scaleNode.x = ${scaleNode.x} scaleNode.y = ${scaleNode.y}`)

                event.stopPropagationImmediate()

                call && call()
            }
        }, touchNode, true)
    }

    /**
     * 比较对象，只比较一层
     * @param obj1 待比较的对象1
     * @param obj2 待比较的对象2
     */
    isEqualObj(obj1: unknown, obj2: unknown): boolean {

        let isEqual = false
        if (obj1 instanceof Array) {
            if (obj2 instanceof Array) {
                if (obj1.length === obj2.length) {
                    isEqual = true
                    for (let index = 0; index < obj1.length; index++) {
                        if (obj1[index] !== obj2[index]) {
                            isEqual = false

                            break
                        }
                    }
                }
            }
        } else if (obj1 instanceof Object) {
            if (obj2 instanceof Object) {
                if (Object.keys(obj1) === Object.keys(obj2)) {
                    isEqual = true
                    for (const key in obj1) {
                        if (Object.prototype.hasOwnProperty.call(obj1, key)) {
                            if (obj1[key] !== obj2[key]) {
                                isEqual = false

                                break
                            }
                        }
                    }
                }
            }
        } else {
            isEqual = obj1 === obj2
        }

        cc.log(`${cc.js.getClassName(this)} isEqualObj isEqual = ${isEqual} obj1 = `, obj1, ` obj2 = `, obj2)

        return isEqual
    }

    /**
     * 截图
     * @param targetNode 指定节点
     */
    screenShot(targetNode: cc.Node = cc.Canvas.instance.node): { filePath: string } {
        cc.log(`${cc.js.getClassName(this)} screenShot`)

        //获取当前场景Camera
        let camera = (new cc.Node(`CaptureCamera`)).addComponent(cc.Camera)
        cc.Canvas.instance.node.addChild(camera.node)
        //创建新的texture
        let texture = new cc.RenderTexture()
        texture.initWithSize(cc.Canvas.instance.node.width, cc.Canvas.instance.node.height)
        //手动渲染camera
        camera.alignWithScreen = false
        camera.orthoSize = targetNode.width
        camera.cullingMask = 0xffffffff
        camera.targetTexture = texture
        camera.render(targetNode)
        camera.targetTexture = null
        camera.node.destroy()

        let path = ''
        if (CC_JSB) {
            let data: Uint8Array = texture.readPixels(),
                nodeWidth = targetNode.width,
                nodeHeight = targetNode.height,
                nodeX = cc.Canvas.instance.node.width / 2 + targetNode.x - nodeWidth / 2,
                nodeY = cc.Canvas.instance.node.height / 2 + targetNode.y - nodeHeight / 2,
                filpYImage = (data: Uint8Array, width: number, height: number) => {
                    let picData = new Uint8Array(width * height * 4)	// 每个像素 4 字节。
                    let rowBytes = width * 4
                    for (let row = 0; row < height; row++) {
                        let srow = height - 1 - row
                        let start = srow * width * 4	// 从最后一行开始递减。
                        let reStart = row * width * 4	// 从第一行开始递增。
                        for (let i = 0; i < rowBytes; i++) {
                            picData[reStart + i] = data[start + i]
                        }
                    }
                    return picData
                }
            data = texture.readPixels(data, nodeX, nodeY, nodeWidth, nodeHeight)
            data = filpYImage(data, nodeWidth, nodeWidth)
            let filePath = jsb.fileUtils.getWritablePath() + `Image.png`
            if ((jsb as any).saveImageData(data, nodeWidth, nodeWidth, filePath)) {
                path = filePath
            }
        }

        return { filePath: path };
    }

    /**
     * 移除数组中的元素
     * @param tarArr 原始数组
     * @param removeArr 要移除的元素数组
     */
    removeFromArr(tarArr: unknown[], removeArr: unknown[]) {
        cc.log(`${cc.js.getClassName(this)} screenShot`)

        removeArr.forEach(remove => {
            tarArr.splice(tarArr.indexOf(remove), 1)
        })
    }

    /**
     * 中心旋转
     * @param startPos 旋转目标坐标
     * @param centerPos 中心坐标
     * @param angle 角度
     */
    rotateAroundCenter(startPos: cc.Vec2, centerPos: cc.Vec2, angle: number): cc.Vec2 {
        cc.log(`${cc.js.getClassName(this)} rotateAroundCenter startPos = ${startPos} centerPos = ${centerPos} angle = ${angle}`)

        let endPos: cc.Vec2 = startPos
        if ((startPos.x !== centerPos.x || startPos.y !== centerPos.y) && angle !== 0) {
            angle = angle % 360 // 将角度转换到 0 ~ 360 
            let radian = angle / 360 * 2 * Math.PI, // 弧度
                tarPosAtCenter = startPos.sub(centerPos) // centerPos 视为原点，tarPos 的新坐标
            endPos = tarPosAtCenter.rotate(radian).add(centerPos)
        }

        return endPos
    }


     // 随机数[)
     public static getRandomValue(max: number, min: number = 0): number {
        return Math.floor(Math.random() * (max - min)) + min;
    }
}